/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "sync_folder_manager_n_exporter.h"

#include <tuple>

#include "cloud_disk_js_manager.h"
#include "dfs_error.h"
#include "utils_log.h"

namespace OHOS::FileManagement::CloudDiskService {
using namespace std;
using namespace LibN;
constexpr const char* PATH = "path";
constexpr const char* DISPLAY_NAME_RES_ID = "displayNameResId";
constexpr const char* DISPLAY_NAME = "displayName";
constexpr const char* STATE = "state";
static napi_status ParseOptionalArguments(napi_env env, napi_value argv, SyncFolder &syncFolder)
{
    napi_value resIdValue = nullptr;
    napi_value displayNameValue = nullptr;
    napi_status status = napi_ok;
    bool isHasResId = false;
    if ((status = napi_has_named_property(env, argv, DISPLAY_NAME_RES_ID, &isHasResId)) == napi_ok && isHasResId) {
        if ((status = napi_get_named_property(env, argv, DISPLAY_NAME_RES_ID, &resIdValue)) != napi_ok) {
            LOGE("get named property displayNameResId failed");
            return status;
        }
    } else {
        LOGW("has no property displayNameResId");
    }
    bool isHasName = false;
    if ((status = napi_has_named_property(env, argv, DISPLAY_NAME, &isHasName)) == napi_ok && isHasName) {
        if ((status = napi_get_named_property(env, argv, DISPLAY_NAME, &displayNameValue)) != napi_ok) {
            LOGE("get named property displayNameResId failed");
            return status;
        }
    } else {
        LOGW("has no property displayName");
    }
    if (isHasResId) {
        auto [succResId, resId] = NVal(env, resIdValue).ToInt32();
        if (!succResId) {
            LOGE("the argument resId error");
            return napi_invalid_arg;
        }
        syncFolder.displayNameResId_ = resId;
    }
    if (isHasName) {
        auto [succdisplayName, strDisplayName, ignore] = NVal(env, displayNameValue).ToUTF8String();
        if (!succdisplayName) {
            LOGE("the argument displayName error");
            return napi_invalid_arg;
        }
        syncFolder.displayName_ = strDisplayName.get();
    }
    return napi_ok;
}

napi_status ParseNecessaryArguments(napi_env env, napi_value argv, SyncFolder &syncFolder)
{
    napi_value pathValue = nullptr;
    napi_status status = napi_ok;
    if ((status = napi_get_named_property(env, argv, PATH, &pathValue)) != napi_ok) {
        LOGE("get named property path failed");
        return status;
    }
    auto [succStr, strPath, ignorePath] = NVal(env, pathValue).ToUTF8String();
    if (!succStr) {
        LOGE("the argument error, succStr = %{public}d", succStr);
        return napi_invalid_arg;
    }
    syncFolder.path_ = strPath.get();
    if (syncFolder.path_.empty()) {
        LOGE("the argument path error");
        return napi_invalid_arg;
    }
    return napi_ok;
}

static napi_status GetSyncFolderFromNapi(napi_env env,
    napi_value argv, SyncFolder &syncFolder)
{
    napi_status status = napi_ok;
    status = ParseNecessaryArguments(env, argv, syncFolder);
    if (status != napi_ok) {
        LOGE("ParseNecessaryArguments failed");
        return status;
    }
    status = ParseOptionalArguments(env, argv, syncFolder);
    if (status != napi_ok) {
        LOGE("ParseOptionalArguments failed");
        return status;
    }
    return napi_ok;
}

static std::tuple<napi_status, napi_value> CreateSyncFolderList(napi_env env,
    const std::vector<SyncFolder> &syncFolders)
{
    napi_value res = nullptr;
    napi_status status = napi_ok;
    if ((status = napi_create_array(env, &res)) != napi_ok) {
        LOGE("Create array failed");
        return { status, res };
    }
    for (size_t i = 0; i < syncFolders.size(); ++i) {
        napi_value ele = nullptr;
        if ((napi_create_object(env, &ele)) != napi_ok) {
            LOGE("Create object failed");
            return { status, res };
        }
        if ((napi_set_named_property(
            env, ele, PATH, NVal::CreateUTF8String(env, syncFolders[i].path_).val_)) != napi_ok) {
            LOGE("Set named property path failed");
            return { status, res };
        }
        if ((status = napi_set_named_property(
            env, ele, STATE, NVal::CreateInt32(env, static_cast<int32_t>(syncFolders[i].state_)).val_)) != napi_ok) {
            LOGE("Set named property state failed");
            return { status, res };
        }
        if (syncFolders[i].displayNameResId_ != -1) {
            if ((status = napi_set_named_property(env, ele, DISPLAY_NAME_RES_ID,
                NVal::CreateInt32(env, syncFolders[i].displayNameResId_).val_)) != napi_ok) {
                LOGE("Set named property resId failed");
                return { status, res };
            }
        }
        if (!syncFolders[i].displayName_.empty()) {
            if ((status = napi_set_named_property(env,
                ele, DISPLAY_NAME, NVal::CreateUTF8String(env, syncFolders[i].displayName_).val_)) != napi_ok) {
                LOGE("Set named property displayName failed");
                return { status, res };
            }
        }
        if ((status = napi_set_element(env, res, i, ele)) != napi_ok) {
            LOGE("Set element failed");
            return { status, res };
        }
    }
    return { napi_ok, res };
}

SyncFolderManagerNExporter::SyncFolderManagerNExporter(napi_env env, napi_value exports): NExporter(env, exports) {}

SyncFolderManagerNExporter::~SyncFolderManagerNExporter() {};


std::string SyncFolderManagerNExporter::GetClassName()
{
    return SyncFolderManagerNExporter::className_;
}
napi_value SyncFolderManagerNExporter::Constructor(napi_env env, napi_callback_info cbinfo)
{
    NFuncArg funcArg(env, cbinfo);
    if (!funcArg.InitArgs(NARG_CNT::ZERO)) {
        LOGE("Number of arguments unmatched");
        NError(ERR_INVALID_PARAM).ThrowErr(env);
        return nullptr;
    }
    unique_ptr<CloudDiskJSManager> cloudDiskJSManager = make_unique<CloudDiskJSManager>();
    if (!NClass::SetEntityFor<CloudDiskJSManager>(env, funcArg.GetThisVar(), std::move(cloudDiskJSManager))) {
        NError(ERR_INVALID_PARAM).ThrowErr(env);
        return nullptr;
    }
    return funcArg.GetThisVar();
}

bool SyncFolderManagerNExporter::Export()
{
    vector<napi_property_descriptor> props = {
        NVal::DeclareNapiFunction("register", Register),
        NVal::DeclareNapiFunction("unregister", Unregister),
        NVal::DeclareNapiFunction("active", Active),
        NVal::DeclareNapiFunction("deactive", Deactive),
        NVal::DeclareNapiFunction("getSyncFolders", GetSyncFolders),
        NVal::DeclareNapiFunction("updateDisplayName", UpdateDisplayName),
    };
    string className = GetClassName();
    bool succ = false;
    napi_value classValue = nullptr;
    std::tie(succ, classValue) = NClass::DefineClass(
        exports_.env_, className, SyncFolderManagerNExporter::Constructor, std::move(props));
    if (!succ) {
        LOGE("Failed to define class %{public}s", className.c_str());
        NError(ERR_INTERNAL_ERROR).ThrowErr(exports_.env_);
        return false;
    }
    succ = NClass::SaveClass(exports_.env_, className, classValue);
    if (!succ) {
        LOGE("Failed to save class %{public}s", className.c_str());
        NError(ERR_INTERNAL_ERROR).ThrowErr(exports_.env_);
        return false;
    }
    return exports_.AddProp(className, classValue);
}

napi_value SyncFolderManagerNExporter::Register(napi_env env, napi_callback_info cbinfo)
{
    NFuncArg funcArg(env, cbinfo);
    if (!funcArg.InitArgs(NARG_CNT::ONE)) {
        LOGE("Number of arguments unmatched");
        NError(ERR_INVALID_PARAM).ThrowErr(env);
        return nullptr;
    }
    SyncFolder syncFolder;
    if (GetSyncFolderFromNapi(env, funcArg[NARG_POS::FIRST], syncFolder) != napi_ok) {
        LOGE("GetSyncFolderFromNapi failed");
        NError(ERR_INVALID_PARAM).ThrowErr(env);
        return nullptr;
    }
    auto syncFolerJSManager = NClass::GetEntityOf<CloudDiskJSManager>(env, funcArg.GetThisVar());
    if (!syncFolerJSManager) {
        LOGE("GetEntityOf syncFolerJSManager failed");
        NError(ERR_INTERNAL_ERROR).ThrowErr(env);
        return nullptr;
    }
    auto cbExec = [syncFolerJSManager, syncFolder]() -> NError {
        if (!syncFolerJSManager) {
            LOGE("syncFolerJSManager is nullptr");
            return NError(ERR_INTERNAL_ERROR);
        }
        int ret = syncFolerJSManager->Register(syncFolder);
        return NError(ret);
    };
    auto cbCompl = [](napi_env env, NError err) -> NVal {
        if (err) {
            return {env, err.GetNapiErr(env)};
        }
        return { NVal::CreateBool(env, true) };
    };
    const string procedureName = "clouddisk_register";
    NVal thisVar(env, funcArg.GetThisVar());
    return NAsyncWorkPromise(env, thisVar).Schedule(procedureName, cbExec, cbCompl).val_;
}

napi_value SyncFolderManagerNExporter::Unregister(napi_env env, napi_callback_info cbinfo)
{
    NFuncArg funcArg(env, cbinfo);
    if (!funcArg.InitArgs(NARG_CNT::ONE)) {
        LOGE("Number of arguments unmatched");
        NError(ERR_INVALID_PARAM).ThrowErr(env);
        return nullptr;
    }
    auto [succStr, str, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
    if (!succStr) {
        LOGE("NVal trans to string failed");
        NError(ERR_INVALID_PARAM).ThrowErr(env);
        return nullptr;
    }
    std::string path = string(str.get());
    if (path.empty()) {
        LOGE("The argument path is empty");
        NError(ERR_INVALID_PARAM).ThrowErr(env);
        return nullptr;
    }
    auto syncFolerJSManager = NClass::GetEntityOf<CloudDiskJSManager>(env, funcArg.GetThisVar());
    if (!syncFolerJSManager) {
        LOGE("GetEntityOf syncFolerJSManager failed");
        NError(ERR_INTERNAL_ERROR).ThrowErr(env);
        return nullptr;
    }
    auto cbExec = [path, syncFolerJSManager]() -> NError {
        if (!syncFolerJSManager) {
            LOGE("syncFolerJSManager is nullptr");
            return NError(ERR_INTERNAL_ERROR);
        }
        int ret = syncFolerJSManager->Unregister(path);
        return NError(ret);
    };
    auto cbCompl = [](napi_env env, NError err) -> NVal {
        if (err) {
            return {env, err.GetNapiErr(env)};
        }
        return { NVal::CreateBool(env, true) };
    };
    const string procedureName = "clouddisk_unregister";
    NVal thisVar(env, funcArg.GetThisVar());
    return NAsyncWorkPromise(env, thisVar).Schedule(procedureName, cbExec, cbCompl).val_;
}

napi_value SyncFolderManagerNExporter::Active(napi_env env, napi_callback_info cbinfo)
{
    NFuncArg funcArg(env, cbinfo);
    if (!funcArg.InitArgs(NARG_CNT::ONE)) {
        LOGE("Number of arguments unmatched");
        NError(ERR_INVALID_PARAM).ThrowErr(env);
        return nullptr;
    }
    auto [succStr, str, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
    if (!succStr) {
        LOGE("NVal trans to string failed");
        NError(ERR_INVALID_PARAM).ThrowErr(env);
        return nullptr;
    }
    std::string path = string(str.get());
    if (path.empty()) {
        LOGE("The argument path is empty");
        NError(ERR_INVALID_PARAM).ThrowErr(env);
        return nullptr;
    }
    auto syncFolerJSManager = NClass::GetEntityOf<CloudDiskJSManager>(env, funcArg.GetThisVar());
    if (!syncFolerJSManager) {
        LOGE("GetEntityOf syncFolerJSManager failed");
        NError(ERR_INVALID_PARAM).ThrowErr(env);
        return nullptr;
    }
    auto cbExec = [path, syncFolerJSManager]() -> NError {
        if (!syncFolerJSManager) {
            LOGE("syncFolerJSManager is nullptr");
            return NError(ERR_INTERNAL_ERROR);
        }
        int ret = syncFolerJSManager->Active(path);
        return NError(ret);
    };
    auto cbCompl = [](napi_env env, NError err) -> NVal {
        if (err) {
            return {env, err.GetNapiErr(env)};
        }
        return { NVal::CreateBool(env, true) };
    };
    const string procedureName = "clouddisk_active";
    NVal thisVar(env, funcArg.GetThisVar());
    return NAsyncWorkPromise(env, thisVar).Schedule(procedureName, cbExec, cbCompl).val_;
}

napi_value SyncFolderManagerNExporter::Deactive(napi_env env, napi_callback_info cbinfo)
{
    NFuncArg funcArg(env, cbinfo);
    if (!funcArg.InitArgs(NARG_CNT::ONE)) {
        LOGE("Number of arguments unmatched");
        NError(ERR_INVALID_PARAM).ThrowErr(env);
        return nullptr;
    }
    auto [succStr, str, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
    if (!succStr) {
        LOGE("NVal trans to string failed");
        NError(ERR_INVALID_PARAM).ThrowErr(env);
        return nullptr;
    }
    std::string path = string(str.get());
    if (path.empty()) {
        LOGE("The argument path is empty");
        NError(ERR_INVALID_PARAM).ThrowErr(env);
        return nullptr;
    }
    auto syncFolerJSManager = NClass::GetEntityOf<CloudDiskJSManager>(env, funcArg.GetThisVar());
    if (!syncFolerJSManager) {
        LOGE("GetEntityOf syncFolerJSManager failed");
        NError(ERR_INTERNAL_ERROR).ThrowErr(env);
        return nullptr;
    }
    auto cbExec = [path, syncFolerJSManager]() -> NError {
        if (!syncFolerJSManager) {
            LOGE("syncFolerJSManager is nullptr");
            return NError(ERR_INTERNAL_ERROR);
        }
        int ret = syncFolerJSManager->Deactive(path);
        return NError(ret);
    };
    auto cbCompl = [](napi_env env, NError err) -> NVal {
        if (err) {
            return {env, err.GetNapiErr(env)};
        }
        return { NVal::CreateBool(env, true) };
    };
    const string procedureName = "clouddisk_inactive";
    NVal thisVar(env, funcArg.GetThisVar());
    return NAsyncWorkPromise(env, thisVar).Schedule(procedureName, cbExec, cbCompl).val_;
}

napi_value SyncFolderManagerNExporter::GetSyncFolders(napi_env env, napi_callback_info cbinfo)
{
    NFuncArg funcArg(env, cbinfo);
    if (!funcArg.InitArgs(NARG_CNT::ZERO)) {
        LOGE("Number of arguments unmatched");
        NError(ERR_INVALID_PARAM).ThrowErr(env);
        return nullptr;
    }
    auto result = std::make_shared<std::vector<SyncFolder>>();
    auto syncFolerJSManager = NClass::GetEntityOf<CloudDiskJSManager>(env, funcArg.GetThisVar());
    if (!syncFolerJSManager) {
        LOGE("GetEntityOf syncFolerJSManager failed");
        NError(ERR_INTERNAL_ERROR).ThrowErr(env);
        return nullptr;
    }
    auto cbExec = [result, syncFolerJSManager]() -> NError {
        if (!syncFolerJSManager) {
            LOGE("syncFolerJSManager is nullptr");
            return NError(ERR_INTERNAL_ERROR);
        }
        int ret = syncFolerJSManager->GetSyncFolders(*result);
        return NError(ret);
    };
    auto cbCompl = [result](napi_env env, NError err) -> NVal {
        if (err) {
            return {env, err.GetNapiErr(env)};
        }
        auto [succSta, syncFolderVec] = CreateSyncFolderList(env, *result);
        if (succSta == napi_ok) {
            return {env, syncFolderVec};
        }
        LOGE("CreateSyncFolderList failed");
        return {env, NError(ERR_INVALID_PARAM).GetNapiErr(env)};
    };
    const string procedureName = "clouddisk_getsyncfolders";
    NVal thisVar(env, funcArg.GetThisVar());
    return NAsyncWorkPromise(env, thisVar).Schedule(procedureName, cbExec, cbCompl).val_;
}

napi_value SyncFolderManagerNExporter::UpdateDisplayName(napi_env env, napi_callback_info cbinfo)
{
    NFuncArg funcArg(env, cbinfo);
    if (!funcArg.InitArgs(NARG_CNT::TWO)) {
        LOGE("Number of arguments unmatched");
        NError(ERR_INVALID_PARAM).ThrowErr(env);
        return nullptr;
    }
    auto [succPath, strPath, ignorePath] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
    auto [succName, strName, ignoreName] = NVal(env, funcArg[NARG_POS::SECOND]).ToUTF8String();
    std::string path = std::string(strPath.get());
    std::string bundleName = std::string(strName.get());
    auto syncFolerJSManager = NClass::GetEntityOf<CloudDiskJSManager>(env, funcArg.GetThisVar());
    if (!syncFolerJSManager) {
        LOGE("GetEntityOf syncFolerJSManager failed");
        NError(ERR_INTERNAL_ERROR).ThrowErr(env);
        return nullptr;
    }
    if (path.empty()) {
        LOGE("The argument path is empty");
        NError(ERR_INVALID_PARAM).ThrowErr(env);
        return nullptr;
    }
    auto cbExec = [path, bundleName, syncFolerJSManager]() -> NError {
        if (!syncFolerJSManager) {
            LOGE("syncFolerJSManager is nullptr");
            return NError(ERR_INTERNAL_ERROR);
        }
        int ret = syncFolerJSManager->UpdateDisplayName(path, bundleName);
        return NError(ret);
    };
    auto cbCompl = [](napi_env env, NError err) -> NVal {
        if (err) {
            return {env, err.GetNapiErr(env)};
        }
        return { NVal::CreateBool(env, true) };
    };
    const string procedureName = "cloud_disk_updatedisplayname";
    NVal thisVar(env, funcArg.GetThisVar());
    return NAsyncWorkPromise(env, thisVar).Schedule(procedureName, cbExec, cbCompl).val_;
}
} // namespace OHOS::FileManagement::CloudDisk
